// =======================================================================
// 
//       Filename:  parser.cpp
// 
//    Description:  
// 
//        Version:  1.0
//        Created:  26.03.2010 19:52:09
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Dominik 'domachine' Burgdörfer (-), dominik.burgdoerfer@googlemail.com
//        Company:  -
// 
// =======================================================================

#include "parser.hpp"
#include "../utils/citerator.hpp"
#include "../utils/regexitermatch.hpp"
#include "../syntaxerror.hpp"
#include "commandelement.hpp"
#include "textelement.hpp"
#include "scriptelement.hpp"
#include "varelement.hpp"
#include "file.hpp"
#include "../types/float.hpp"
#include "../types/integer.hpp"
#include <boost/regex.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <fstream>

namespace webplodder {
    namespace templatefile {
        Parser::Parser()
            : mLine(0)
        {

        }

        void Parser::parse(templatefile::File& templateFile)

        {
            std::ifstream input(templateFile.sourcePath().string().c_str());
            mIterator.setStream(input);
            mLine = 1;
            bool textBlock = false;
            templatefile::TextElement* textElement = 0;

            for(; mIterator.good(); mIterator.moveForward()) {
                if(mIterator.currentChar() != '{') {
                    if(!textBlock) {
                        textElement = new templatefile::TextElement(templateFile,
                                mIterator.position(), -1);

                        textBlock = true;
                    }

                    if(mCacheMode == CACHE_ALL)
                        (*textElement) << mIterator.currentChar();
                    if(mIterator.currentChar() == '\n')
                        ++mLine;
                }
                else {
                    if(mIterator.nextChar() == '{') {
                        if(mCacheMode == CACHE_ALL)
                            (*textElement) << mIterator.currentChar();
                        safelyIncr();
                        continue;
                    }

                    if(textBlock) {
                        textElement->setEnd(mIterator.position());
                        templateFile.push_back(
                                boost::shared_ptr<templatefile::TextElement>(
                                    textElement));
                        textBlock = false;
                    }

                    templateFile.push_back(parseCmd(templateFile));
                }
            }

            if(textElement->end() == -1) {
                textElement->setEnd(mIterator.position());
                templateFile.push_back(
                        boost::shared_ptr<templatefile::TextElement>(
                            textElement));
            }
        }

        utils::CIterator* Parser::iterator()
        {
            return &mIterator;
        }

        void Parser::setCacheMode(CacheMode mode)
        {
            mCacheMode = mode;
        }

        void Parser::safelyIncr()
        {
            mIterator.moveForward();

            if(!mIterator.good())
                throw SyntaxError("Unexpected end-of-stream.",
                        mLine);
        }

        void Parser::skipWhiteSpace()
        {
            boost::regex spaceregex("[ \t\n]");

            while(utils::regexIterMatch(mIterator, spaceregex)) {
                if(mIterator.currentChar() == '\n')
                    ++mLine;

                safelyIncr();
            }
        }

        boost::shared_ptr<templatefile::ScriptElement> Parser::parseScript(
                templatefile::File& templateFile)

        {
            boost::regex scriptregex("[a-z0-9\\-_A-Z]");
            std::string scriptSpec;
            boost::shared_ptr<templatefile::ScriptElement> scriptElement( 
                    new templatefile::ScriptElement(templateFile));

            safelyIncr();

            skipWhiteSpace();

            while(utils::regexIterMatch(mIterator, scriptregex)) {
                scriptSpec.push_back(mIterator.currentChar());
                safelyIncr();
            }

            if(scriptSpec.size() == 0)
                throw SyntaxError("No script specification",
                        mLine);

            scriptElement->setLine(mLine);

            skipWhiteSpace();

            scriptElement->setBegin(mIterator.position());

            while(true) {
                if(mIterator.currentChar() == '!' && 
                        mIterator.nextChar() == '}') {
                    safelyIncr();
                    scriptElement->setEnd(mIterator.position());
                    break;
                }
                else {
                    char cChar = mIterator.currentChar();
                    if(mCacheMode == CACHE_ALL || mCacheMode == CACHE_SCRIPT)
                        (*scriptElement) << cChar;
                    if(cChar == '\n')
                        mLine++;
                    safelyIncr();
                }
            }

            scriptElement->setSpecification(scriptSpec);

            return scriptElement;
        }

        boost::shared_ptr<templatefile::VarElement> Parser::parseVar(templatefile::File& templateFile)
        {
            std::string cmdName;
            boost::regex cmdregex("[a-zA-Z_0-9]");

            safelyIncr();
            skipWhiteSpace();
            while(utils::regexIterMatch(mIterator, cmdregex)) {
                cmdName.push_back(mIterator.currentChar());
                safelyIncr();
            }

            boost::shared_ptr<templatefile::VarElement> varElement(
                    new templatefile::VarElement(templateFile));
            varElement->setName(cmdName);
            varElement->setLine(mLine);

            skipWhiteSpace();

            if(mIterator.currentChar() != '}')
                throw SyntaxError(
                        "unexpected char, expected '}'",
                        mLine);
            return varElement;
        }

        boost::shared_ptr<templatefile::Element> 
            Parser::parseCmd(templatefile::File& templateFile) 
            {
                std::string cmdName;
                int cmdLine;
                bool braceOpen = false;
                boost::regex cmdregex("[a-zA-Z_0-9]");

                braceOpen = true;
                safelyIncr();

                skipWhiteSpace();

                if(mIterator.currentChar() == '!') {
                    return parseScript(templateFile);
                }
                else if(mIterator.currentChar() == '$') {
                    return parseVar(templateFile);
                }

                while(utils::regexIterMatch(mIterator, cmdregex)) {
                    cmdName.push_back(mIterator.currentChar());
                    safelyIncr();
                }

                if(cmdName.size() == 0)
                    throw SyntaxError("no or no valid command name",
                            mLine);

                cmdLine = mLine;

                skipWhiteSpace();

                std::vector<boost::shared_ptr<types::Object> > args;

                if(mIterator.currentChar() == ':') {
                    safelyIncr();

                    skipWhiteSpace();

                    std::string currentArg;
                    while(true) {
                        if(mIterator.currentChar() == '"' ||
                                mIterator.currentChar() == '\'') {
                            args.push_back(parseString());
                            safelyIncr();
                            skipWhiteSpace();
                        }
                        else if(mIterator.currentChar() == '{') {
                            args.push_back(parseCmd(
                                        templateFile));

                            safelyIncr();
                            skipWhiteSpace();
                        }
                        else if(mIterator.currentChar() == '}')
                            break;
                        else if(mIterator.currentChar() == ',') {
                            safelyIncr();
                            skipWhiteSpace();
                        }
                        else {
                            args.push_back(parseNumber());
                            skipWhiteSpace();
                        }
                    }
                }
                else if(mIterator.currentChar() != '}') {
                    throw SyntaxError(
                            "unexpected char, expected '}' or ':'",
                            mLine);
                }

                if(cmdName == "set" && mIterator.nextChar() == '\n') {
                    safelyIncr();
                    mLine++;
                }

                boost::shared_ptr<templatefile::CommandElement> element(
                        new templatefile::CommandElement(templateFile, 
                            cmdName, args));

                element->setLine(cmdLine);
                return element;
            }

        boost::shared_ptr<types::String> Parser::parseString() {
            char delimiter = '"';
            boost::shared_ptr<types::String>
                buffer(new types::String(""));

            if(mIterator.currentChar() == '\'')
                delimiter = '\'';

            char charBeforeLast;

            do {
                charBeforeLast = mIterator.lastChar();
                safelyIncr();

                if(mIterator.currentChar() == '\\')
                    if(mIterator.lastChar() != '\\')
                        continue;

                if(mIterator.currentChar() == delimiter)
                    if(mIterator.lastChar() != '\\' || charBeforeLast == '\\')
                        break;

                buffer->push_back(mIterator.currentChar());

                if(mIterator.currentChar() == '\n') {
                    if(mIterator.lastChar() == '\\' &&
                            charBeforeLast != '\\')
                        buffer->erase(buffer->end()-1);
                    ++mLine;
                }
            } while(true);

            return buffer;
        }

        boost::shared_ptr<types::Object> Parser::parseNumber() {
            boost::regex numRegex("[0-9\\.\\-]");
            std::string buffer;

            while(utils::regexIterMatch(mIterator, numRegex)) {
                buffer.push_back(mIterator.currentChar());
                safelyIncr();
            }

            if(buffer.empty())
                throw SyntaxError("unexpected token", 
                        mLine);
            else {
                boost::regex floatRegex("\\-?[0-9]+\\.[0-9]+");
                boost::regex intRegex("\\-?[0-9]+");

                if(boost::regex_match(buffer, floatRegex)) {
                    double f = atof(buffer.c_str());

                    if(f == HUGE_VAL || f == -HUGE_VAL)
                        throw SyntaxError("double value out of range",
                                mLine);

                    boost::shared_ptr<types::Float> floatObj(
                            new types::Float(f));

                    return floatObj;
                }
                else if(boost::regex_match(buffer, intRegex)) {
                    int i = atoi(buffer.c_str());

                    if(i == INT_MAX || i == INT_MIN)
                        throw SyntaxError("integer value out of range",
                                mLine);

                    boost::shared_ptr<types::Integer> 
                        integer(new types::Integer(i));
                    return integer;
                }
                else 
                    throw SyntaxError("unexpected token", 
                            mLine);
            }
        }
    }
}
